/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture size tests.
 *//*--------------------------------------------------------------------*/

#include "es3fTextureSizeTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluTexture.hpp"
#include "gluStrUtil.hpp"
#include "gluTextureUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"

#include "glwEnums.hpp"
#include "glwFunctions.hpp"

namespace deqp
{
namespace gles3
{
namespace Functional
{

using tcu::TestLog;
using std::vector;
using std::string;
using tcu::Sampler;
using namespace glu;
using namespace gls::TextureTestUtil;
using namespace glu::TextureTestUtil;


class Texture2DSizeCase : public tcu::TestCase
{
public:
							Texture2DSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
							~Texture2DSizeCase		(void);

	void					init					(void);
	void					deinit					(void);
	IterateResult			iterate					(void);

private:
							Texture2DSizeCase		(const Texture2DSizeCase& other);
	Texture2DSizeCase&		operator=				(const Texture2DSizeCase& other);

	glu::RenderContext&		m_renderCtx;

	deUint32				m_format;
	deUint32				m_dataType;
	int						m_width;
	int						m_height;
	bool					m_useMipmaps;

	glu::Texture2D*			m_texture;
	TextureRenderer			m_renderer;
};

Texture2DSizeCase::Texture2DSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
	: TestCase		(testCtx, name, description)
	, m_renderCtx	(renderCtx)
	, m_format		(format)
	, m_dataType	(dataType)
	, m_width		(width)
	, m_height		(height)
	, m_useMipmaps	(mipmaps)
	, m_texture		(DE_NULL)
	, m_renderer	(renderCtx, testCtx.getLog(), glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
{
}

Texture2DSizeCase::~Texture2DSizeCase (void)
{
	Texture2DSizeCase::deinit();
}

void Texture2DSizeCase::init (void)
{
	DE_ASSERT(!m_texture);
	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);

	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;

	// Fill levels.
	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
	{
		m_texture->getRefTexture().allocLevel(levelNdx);
		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(levelNdx), tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
	}
}

void Texture2DSizeCase::deinit (void)
{
	delete m_texture;
	m_texture = DE_NULL;

	m_renderer.clear();
}

Texture2DSizeCase::IterateResult Texture2DSizeCase::iterate (void)
{
	const glw::Functions&	gl				= m_renderCtx.getFunctions();
	TestLog&				log				= m_testCtx.getLog();
	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName()));
	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
	const tcu::IVec4		texBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
	const tcu::PixelFormat&	rtFmt			= m_renderCtx.getRenderTarget().getPixelFormat();
	const tcu::PixelFormat	thresholdFormat(de::min(texBits[0], rtFmt.redBits), de::min(texBits[1], rtFmt.greenBits), de::min(texBits[2], rtFmt.blueBits), de::min(texBits[3], rtFmt.alphaBits));
	tcu::RGBA				threshold		= thresholdFormat.getColorThreshold() + tcu::RGBA(7,7,7,7);
	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
	// Do not minify with GL_NEAREST. A large POT texture with a small POT render target will produce
	// indeterminate results.
	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR;
	deUint32				magFilter		= GL_NEAREST;
	vector<float>			texCoord;

	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));

	// Setup base viewport.
	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);

	// Upload texture data to GL.
	m_texture->upload();

	// Bind to unit 0.
	gl.activeTexture(GL_TEXTURE0);
	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());

	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	minFilter);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");

	// Draw.
	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());

	// Compute reference.
	sampleTexture(tcu::SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_2D, mapGLSampler(wrapS, wrapT, minFilter, magFilter)));

	// Compare and log.
	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);

	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
							isOk ? "Pass"				: "Image comparison failed");

	return STOP;
}

class TextureCubeSizeCase : public tcu::TestCase
{
public:
							TextureCubeSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
							~TextureCubeSizeCase	(void);

	void					init					(void);
	void					deinit					(void);
	IterateResult			iterate					(void);

private:
							TextureCubeSizeCase		(const TextureCubeSizeCase& other);
	TextureCubeSizeCase&	operator=				(const TextureCubeSizeCase& other);

	bool					testFace				(tcu::CubeFace face);

	glu::RenderContext&		m_renderCtx;

	deUint32				m_format;
	deUint32				m_dataType;
	int						m_width;
	int						m_height;
	bool					m_useMipmaps;

	glu::TextureCube*		m_texture;
	TextureRenderer			m_renderer;

	int						m_curFace;
	bool					m_isOk;
};

TextureCubeSizeCase::TextureCubeSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
	: TestCase		(testCtx, name, description)
	, m_renderCtx	(renderCtx)
	, m_format		(format)
	, m_dataType	(dataType)
	, m_width		(width)
	, m_height		(height)
	, m_useMipmaps	(mipmaps)
	, m_texture		(DE_NULL)
	, m_renderer	(renderCtx, testCtx.getLog(), glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
	, m_curFace		(0)
	, m_isOk		(false)
{
}

TextureCubeSizeCase::~TextureCubeSizeCase (void)
{
	TextureCubeSizeCase::deinit();
}

void TextureCubeSizeCase::init (void)
{
	DE_ASSERT(!m_texture);
	DE_ASSERT(m_width == m_height);
	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_width);

	static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
	{
		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
		{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
		{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
		{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
		{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
	};

	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;

	// Fill levels.
	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
	{
		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		{
			m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
			fillWithComponentGradients(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0], gradients[face][1]);
		}
	}

	// Upload texture data to GL.
	m_texture->upload();

	// Initialize iteration state.
	m_curFace	= 0;
	m_isOk		= true;
}

void TextureCubeSizeCase::deinit (void)
{
	delete m_texture;
	m_texture = DE_NULL;

	m_renderer.clear();
}

bool TextureCubeSizeCase::testFace (tcu::CubeFace face)
{
	const glw::Functions&	gl				= m_renderCtx.getFunctions();
	TestLog&				log				= m_testCtx.getLog();
	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName())+(deUint32)face);
	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
	const tcu::IVec4		texBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
	const tcu::PixelFormat&	rtFmt			= m_renderCtx.getRenderTarget().getPixelFormat();
	const tcu::PixelFormat	thresholdFormat(de::min(texBits[0], rtFmt.redBits), de::min(texBits[1], rtFmt.greenBits), de::min(texBits[2], rtFmt.blueBits), de::min(texBits[3], rtFmt.alphaBits));
	tcu::RGBA				threshold		= thresholdFormat.getColorThreshold() + tcu::RGBA(7,7,7,7);
	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
	// Do not minify with GL_NEAREST. A large POT texture with a small POT render target will produce
	// indeterminate results.
	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR;
	deUint32				magFilter		= GL_NEAREST;
	vector<float>			texCoord;

	computeQuadTexCoordCube(texCoord, face);

	// \todo [2011-10-28 pyry] Image set name / section?
	log << TestLog::Message << face << TestLog::EndMessage;

	// Setup base viewport.
	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);

	// Bind to unit 0.
	gl.activeTexture(GL_TEXTURE0);
	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());

	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapS);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapT);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, minFilter);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, magFilter);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");

	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());

	// Compute reference.
	Sampler sampler = mapGLSampler(wrapS, wrapT, minFilter, magFilter);
	sampler.seamlessCubeMap = true;
	sampleTexture(tcu::SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_CUBE, sampler));

	// Compare and log.
	return compareImages(log, referenceFrame, renderedFrame, threshold);
}

TextureCubeSizeCase::IterateResult TextureCubeSizeCase::iterate (void)
{
	// Execute test for all faces.
	if (!testFace((tcu::CubeFace)m_curFace))
		m_isOk = false;

	m_curFace += 1;

	if (m_curFace == tcu::CUBEFACE_LAST)
	{
		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								m_isOk ? "Pass"					: "Image comparison failed");
		return STOP;
	}
	else
		return CONTINUE;
}

TextureSizeTests::TextureSizeTests (Context& context)
	: TestCaseGroup(context, "size", "Texture Size Tests")
{
}

TextureSizeTests::~TextureSizeTests (void)
{
}

void TextureSizeTests::init (void)
{
	struct
	{
		int	width;
		int	height;
	} sizes2D[] =
	{
		{   64,   64 }, // Spec-mandated minimum.
		{   65,   63 },
		{  512,  512 },
		{ 1024, 1024 },
		{ 2048, 2048 }
	};

	struct
	{
		int	width;
		int	height;
	} sizesCube[] =
	{
		{  15,  15 },
		{  16,  16 }, // Spec-mandated minimum
		{  64,  64 },
		{ 128, 128 },
		{ 256, 256 },
		{ 512, 512 }
	};

	struct
	{
		const char*	name;
		deUint32	format;
		deUint32	dataType;
	} formats[] =
	{
		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
	};

	// 2D cases.
	tcu::TestCaseGroup* group2D = new tcu::TestCaseGroup(m_testCtx, "2d", "2D Texture Size Tests");
	addChild(group2D);
	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes2D); sizeNdx++)
	{
		int		width	= sizes2D[sizeNdx].width;
		int		height	= sizes2D[sizeNdx].height;
		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);

		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
		{
			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
			{
				std::ostringstream name;
				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");

				group2D->addChild(new Texture2DSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
														formats[formatNdx].format, formats[formatNdx].dataType,
														width, height, mipmap != 0));
			}
		}
	}

	// Cubemap cases.
	tcu::TestCaseGroup* groupCube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cubemap Texture Size Tests");
	addChild(groupCube);
	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizesCube); sizeNdx++)
	{
		int		width	= sizesCube[sizeNdx].width;
		int		height	= sizesCube[sizeNdx].height;
		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);

		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
		{
			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
			{
				std::ostringstream name;
				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");

				groupCube->addChild(new TextureCubeSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
															formats[formatNdx].format, formats[formatNdx].dataType,
															width, height, mipmap != 0));
			}
		}
	}
}

} // Functional
} // gles3
} // deqp
